Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b7ec5341cf | |||
| 9e5410d617 |
+729
-309
File diff suppressed because it is too large
Load Diff
@@ -159,6 +159,8 @@ class XauStrategyConfig:
|
||||
rsi_zone: tuple = (40.0, 65.0)
|
||||
allowed_weekdays: List[int] = field(default_factory=lambda: [0, 1, 2, 3, 4, 6]) # Mon-Fri + Sun (Sydney open)
|
||||
sell_min_quality: List[str] = field(default_factory=lambda: ["A+", "A", "B"])
|
||||
be_trigger_r: float = 0.0
|
||||
be_buffer_r: float = 0.0
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -169,24 +171,24 @@ class EurUsdStrategyConfig:
|
||||
weekend_filter: bool = True
|
||||
allowed_weekdays: List[int] = field(default_factory=lambda: [0, 1, 2, 3, 4])
|
||||
min_atr_threshold: float = 0.0003
|
||||
sl_atr_multiplier: float = 0.5
|
||||
tp_atr_multiplier: float = 1.5
|
||||
sl_atr_multiplier: float = 1.5
|
||||
tp_atr_multiplier: float = 1.0
|
||||
|
||||
sl_atr_floor: float = 1.2
|
||||
min_rr_ratio: float = 1.0
|
||||
min_rr_ratio: float = 0.5
|
||||
mature_move_threshold: float = 35.0
|
||||
tp_quality_scale: Dict[str, float] = field(default_factory=lambda: {
|
||||
"A+": 1.0, "A": 1.0, "B": 1.0, "C": 1.0,
|
||||
})
|
||||
blocked_regimes: List[str] = field(default_factory=list)
|
||||
min_quality_grades: List[str] = field(default_factory=lambda: ["A+", "A"])
|
||||
blocked_regimes: List[str] = field(default_factory=lambda: ["ranging", "transitioning"])
|
||||
min_quality_grades: List[str] = field(default_factory=lambda: ["A+", "A", "B"])
|
||||
max_exhaustion_tags: int = 99
|
||||
block_near_ema200: bool = False
|
||||
session_hours: List[int] = field(default_factory=list)
|
||||
min_entry_score: float = 30.0
|
||||
min_entry_confidence: float = 50.0
|
||||
min_entry_score: float = 38.0
|
||||
min_entry_confidence: float = 60.0
|
||||
block_rsi_extended: bool = True
|
||||
be_trigger_r: float = 0.3
|
||||
be_trigger_r: float = 0.2
|
||||
be_buffer_r: float = 0.05
|
||||
|
||||
|
||||
|
||||
@@ -80,8 +80,9 @@ def make_config(
|
||||
config.eur_strategy.sl_atr_floor = 0.8
|
||||
config.eur_strategy.sl_atr_multiplier = 0.3
|
||||
config.eur_strategy.min_quality_grades = ["A+", "A", "B"]
|
||||
config.eur_strategy.min_entry_score = 28.0
|
||||
config.eur_strategy.min_entry_score = 35.0
|
||||
config.eur_strategy.min_entry_confidence = 50.0
|
||||
config.backtest.blocked_hours_utc = [22, 23, 0, 1, 2, 3, 4, 19, 21]
|
||||
else:
|
||||
config.technical.regime_weights_override = EUR_REGIME_WEIGHTS
|
||||
|
||||
|
||||
@@ -20,4 +20,9 @@ def make_config(
|
||||
config.confirmation_timeframes = (
|
||||
confirmation_tfs or _CONFIRMATIONS.get(primary_tf, [Timeframe.M15, Timeframe.H4])
|
||||
)
|
||||
|
||||
if primary_tf == Timeframe.M5:
|
||||
config.xau_strategy.be_trigger_r = 0.15
|
||||
config.xau_strategy.be_buffer_r = 0.05
|
||||
|
||||
return config
|
||||
|
||||
@@ -498,7 +498,8 @@ class SignalAggregator:
|
||||
if report.primary_analysis else 0)
|
||||
if atr > 0:
|
||||
report.trade_setup = self._generate_xau_atr_trade_setup(report, atr)
|
||||
if report.trade_setup is None and report.signal_quality in ("A+", "A", "B", "C"):
|
||||
setup_grades = ("A+", "A", "B", "C")
|
||||
if report.trade_setup is None and report.signal_quality in setup_grades:
|
||||
report.trade_setup = self._generate_trade_setup(report, pip_value)
|
||||
|
||||
# ── 8. Reasons ─────────────────────────────────────────────
|
||||
|
||||
+4
-5
@@ -1656,15 +1656,14 @@ def _():
|
||||
assert 6 not in cfg.allowed_weekdays # No Sunday
|
||||
assert cfg.min_atr_threshold == 0.0003
|
||||
assert cfg.sl_atr_floor == 1.2
|
||||
assert cfg.min_rr_ratio == 1.0
|
||||
assert cfg.min_rr_ratio == 0.5
|
||||
assert cfg.mature_move_threshold == 35.0
|
||||
assert cfg.tp_quality_scale["A+"] == 1.0
|
||||
assert cfg.tp_quality_scale["A"] == 1.0
|
||||
assert cfg.tp_quality_scale["C"] == 1.0
|
||||
assert cfg.blocked_regimes == []
|
||||
assert cfg.blocked_regimes == ["ranging", "transitioning"]
|
||||
assert "A" in cfg.min_quality_grades
|
||||
assert "A" in cfg.min_quality_grades
|
||||
assert "B" not in cfg.min_quality_grades
|
||||
assert "B" in cfg.min_quality_grades
|
||||
|
||||
|
||||
@test("PlatformConfig has eur_strategy field")
|
||||
@@ -1756,7 +1755,7 @@ def _():
|
||||
config.eur_strategy.blocked_regimes = []
|
||||
config.eur_strategy.min_quality_grades = []
|
||||
assert config.eur_strategy.sl_atr_floor == 1.2
|
||||
assert config.eur_strategy.min_rr_ratio == 1.0
|
||||
assert config.eur_strategy.min_rr_ratio == 0.5
|
||||
agg = SignalAggregator(config)
|
||||
df = make_ohlcv(n=300, start_price=1.0800, trend=0.0003, seed=77)
|
||||
dfs = {Timeframe.H1: df, Timeframe.M15: df, Timeframe.H4: df}
|
||||
|
||||
+51
-7
@@ -86,6 +86,8 @@ _LEVEL_KEYS = (
|
||||
_signal_stats: Dict[str, Dict[str, SignalReport]] = {}
|
||||
_stats_updated: Optional[float] = None
|
||||
|
||||
_last_sent: Dict[str, Dict] = {}
|
||||
|
||||
SCAN_BUTTONS = [
|
||||
{"cb": "scan_xauusd_m5", "symbol": XAUUSD, "tf": Timeframe.M5, "label": "XAU 5m"},
|
||||
{"cb": "scan_xauusd_m15", "symbol": XAUUSD, "tf": Timeframe.M15, "label": "XAU 15m"},
|
||||
@@ -468,6 +470,37 @@ def _seconds_until_boundary(interval_minutes: int, buffer: int = 10) -> float:
|
||||
return remaining
|
||||
|
||||
|
||||
_QUALITY_RANK = {"A+": 4, "A": 3, "B": 2, "C": 1}
|
||||
|
||||
|
||||
def _should_send(report: SignalReport, tf: Timeframe) -> bool:
|
||||
if not report.trade_setup:
|
||||
return False
|
||||
key = f"{report.symbol}:{tf.value}"
|
||||
prev = _last_sent.get(key)
|
||||
if prev is None:
|
||||
return True
|
||||
direction = report.trade_setup.direction
|
||||
quality = report.signal_quality
|
||||
if direction != prev["direction"]:
|
||||
return True
|
||||
if _QUALITY_RANK.get(quality, 0) > _QUALITY_RANK.get(prev["quality"], 0):
|
||||
return True
|
||||
cooldown = 5 * _TF_SECONDS.get(tf, 300)
|
||||
if time.time() - prev["sent_at"] >= cooldown:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _record_sent(report: SignalReport, tf: Timeframe) -> None:
|
||||
key = f"{report.symbol}:{tf.value}"
|
||||
_last_sent[key] = {
|
||||
"direction": report.trade_setup.direction,
|
||||
"quality": report.signal_quality,
|
||||
"sent_at": time.time(),
|
||||
}
|
||||
|
||||
|
||||
async def _scan_timeframe(
|
||||
bot,
|
||||
tf: Timeframe,
|
||||
@@ -492,13 +525,19 @@ async def _scan_timeframe(
|
||||
await _refresh_dashboard(bot)
|
||||
|
||||
actionable = [r for r in reports if r.signal_quality in ("A+", "A", "B", "C")]
|
||||
for chat_id in list(_autoscan_chats):
|
||||
for report in actionable:
|
||||
detail = format_signal_message(report)
|
||||
try:
|
||||
await bot.send_message(chat_id, detail, parse_mode=ParseMode.MARKDOWN)
|
||||
except Exception as exc:
|
||||
logger.warning(f"Failed to send signal for chat {chat_id}: {exc}")
|
||||
sendable = [r for r in actionable if _should_send(r, tf)]
|
||||
for report in sendable:
|
||||
_record_sent(report, tf)
|
||||
if sendable:
|
||||
for chat_id in list(_autoscan_chats):
|
||||
for report in sendable:
|
||||
detail = format_signal_message(report)
|
||||
try:
|
||||
await bot.send_message(chat_id, detail, parse_mode=ParseMode.MARKDOWN)
|
||||
except Exception as exc:
|
||||
logger.warning(f"Failed to send signal for chat {chat_id}: {exc}")
|
||||
elif actionable:
|
||||
logger.info(f"Auto-scan {tf.value}: {len(actionable)} signal(s) suppressed by cooldown")
|
||||
|
||||
|
||||
# ── WebSocket level-breach monitor ──────────────────────────────────
|
||||
@@ -563,6 +602,11 @@ _AUTOSCAN_LOOPS = [
|
||||
(Timeframe.H4, 240),
|
||||
]
|
||||
|
||||
_TF_SECONDS = {
|
||||
Timeframe.M5: 300, Timeframe.M15: 900,
|
||||
Timeframe.H1: 3600, Timeframe.H4: 14400,
|
||||
}
|
||||
|
||||
|
||||
def _start_scheduler(bot) -> None:
|
||||
global _dashboard_task
|
||||
|
||||
Reference in New Issue
Block a user