Compare commits
2 Commits
d6c6333ffb
...
b7ec5341cf
| 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)
|
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)
|
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"])
|
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
|
@dataclass
|
||||||
@@ -169,24 +171,24 @@ class EurUsdStrategyConfig:
|
|||||||
weekend_filter: bool = True
|
weekend_filter: bool = True
|
||||||
allowed_weekdays: List[int] = field(default_factory=lambda: [0, 1, 2, 3, 4])
|
allowed_weekdays: List[int] = field(default_factory=lambda: [0, 1, 2, 3, 4])
|
||||||
min_atr_threshold: float = 0.0003
|
min_atr_threshold: float = 0.0003
|
||||||
sl_atr_multiplier: float = 0.5
|
sl_atr_multiplier: float = 1.5
|
||||||
tp_atr_multiplier: float = 1.5
|
tp_atr_multiplier: float = 1.0
|
||||||
|
|
||||||
sl_atr_floor: float = 1.2
|
sl_atr_floor: float = 1.2
|
||||||
min_rr_ratio: float = 1.0
|
min_rr_ratio: float = 0.5
|
||||||
mature_move_threshold: float = 35.0
|
mature_move_threshold: float = 35.0
|
||||||
tp_quality_scale: Dict[str, float] = field(default_factory=lambda: {
|
tp_quality_scale: Dict[str, float] = field(default_factory=lambda: {
|
||||||
"A+": 1.0, "A": 1.0, "B": 1.0, "C": 1.0,
|
"A+": 1.0, "A": 1.0, "B": 1.0, "C": 1.0,
|
||||||
})
|
})
|
||||||
blocked_regimes: List[str] = field(default_factory=list)
|
blocked_regimes: List[str] = field(default_factory=lambda: ["ranging", "transitioning"])
|
||||||
min_quality_grades: List[str] = field(default_factory=lambda: ["A+", "A"])
|
min_quality_grades: List[str] = field(default_factory=lambda: ["A+", "A", "B"])
|
||||||
max_exhaustion_tags: int = 99
|
max_exhaustion_tags: int = 99
|
||||||
block_near_ema200: bool = False
|
block_near_ema200: bool = False
|
||||||
session_hours: List[int] = field(default_factory=list)
|
session_hours: List[int] = field(default_factory=list)
|
||||||
min_entry_score: float = 30.0
|
min_entry_score: float = 38.0
|
||||||
min_entry_confidence: float = 50.0
|
min_entry_confidence: float = 60.0
|
||||||
block_rsi_extended: bool = True
|
block_rsi_extended: bool = True
|
||||||
be_trigger_r: float = 0.3
|
be_trigger_r: float = 0.2
|
||||||
be_buffer_r: float = 0.05
|
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_floor = 0.8
|
||||||
config.eur_strategy.sl_atr_multiplier = 0.3
|
config.eur_strategy.sl_atr_multiplier = 0.3
|
||||||
config.eur_strategy.min_quality_grades = ["A+", "A", "B"]
|
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.eur_strategy.min_entry_confidence = 50.0
|
||||||
|
config.backtest.blocked_hours_utc = [22, 23, 0, 1, 2, 3, 4, 19, 21]
|
||||||
else:
|
else:
|
||||||
config.technical.regime_weights_override = EUR_REGIME_WEIGHTS
|
config.technical.regime_weights_override = EUR_REGIME_WEIGHTS
|
||||||
|
|
||||||
|
|||||||
@@ -20,4 +20,9 @@ def make_config(
|
|||||||
config.confirmation_timeframes = (
|
config.confirmation_timeframes = (
|
||||||
confirmation_tfs or _CONFIRMATIONS.get(primary_tf, [Timeframe.M15, Timeframe.H4])
|
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
|
return config
|
||||||
|
|||||||
@@ -498,7 +498,8 @@ class SignalAggregator:
|
|||||||
if report.primary_analysis else 0)
|
if report.primary_analysis else 0)
|
||||||
if atr > 0:
|
if atr > 0:
|
||||||
report.trade_setup = self._generate_xau_atr_trade_setup(report, atr)
|
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)
|
report.trade_setup = self._generate_trade_setup(report, pip_value)
|
||||||
|
|
||||||
# ── 8. Reasons ─────────────────────────────────────────────
|
# ── 8. Reasons ─────────────────────────────────────────────
|
||||||
|
|||||||
+4
-5
@@ -1656,15 +1656,14 @@ def _():
|
|||||||
assert 6 not in cfg.allowed_weekdays # No Sunday
|
assert 6 not in cfg.allowed_weekdays # No Sunday
|
||||||
assert cfg.min_atr_threshold == 0.0003
|
assert cfg.min_atr_threshold == 0.0003
|
||||||
assert cfg.sl_atr_floor == 1.2
|
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.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["A"] == 1.0
|
assert cfg.tp_quality_scale["A"] == 1.0
|
||||||
assert cfg.tp_quality_scale["C"] == 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 "A" in cfg.min_quality_grades
|
assert "B" in cfg.min_quality_grades
|
||||||
assert "B" not in cfg.min_quality_grades
|
|
||||||
|
|
||||||
|
|
||||||
@test("PlatformConfig has eur_strategy field")
|
@test("PlatformConfig has eur_strategy field")
|
||||||
@@ -1756,7 +1755,7 @@ def _():
|
|||||||
config.eur_strategy.blocked_regimes = []
|
config.eur_strategy.blocked_regimes = []
|
||||||
config.eur_strategy.min_quality_grades = []
|
config.eur_strategy.min_quality_grades = []
|
||||||
assert config.eur_strategy.sl_atr_floor == 1.2
|
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)
|
agg = SignalAggregator(config)
|
||||||
df = make_ohlcv(n=300, start_price=1.0800, trend=0.0003, seed=77)
|
df = make_ohlcv(n=300, start_price=1.0800, trend=0.0003, seed=77)
|
||||||
dfs = {Timeframe.H1: df, Timeframe.M15: df, Timeframe.H4: df}
|
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]] = {}
|
_signal_stats: Dict[str, Dict[str, SignalReport]] = {}
|
||||||
_stats_updated: Optional[float] = None
|
_stats_updated: Optional[float] = None
|
||||||
|
|
||||||
|
_last_sent: Dict[str, Dict] = {}
|
||||||
|
|
||||||
SCAN_BUTTONS = [
|
SCAN_BUTTONS = [
|
||||||
{"cb": "scan_xauusd_m5", "symbol": XAUUSD, "tf": Timeframe.M5, "label": "XAU 5m"},
|
{"cb": "scan_xauusd_m5", "symbol": XAUUSD, "tf": Timeframe.M5, "label": "XAU 5m"},
|
||||||
{"cb": "scan_xauusd_m15", "symbol": XAUUSD, "tf": Timeframe.M15, "label": "XAU 15m"},
|
{"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
|
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(
|
async def _scan_timeframe(
|
||||||
bot,
|
bot,
|
||||||
tf: Timeframe,
|
tf: Timeframe,
|
||||||
@@ -492,13 +525,19 @@ async def _scan_timeframe(
|
|||||||
await _refresh_dashboard(bot)
|
await _refresh_dashboard(bot)
|
||||||
|
|
||||||
actionable = [r for r in reports if r.signal_quality in ("A+", "A", "B", "C")]
|
actionable = [r for r in reports if r.signal_quality in ("A+", "A", "B", "C")]
|
||||||
for chat_id in list(_autoscan_chats):
|
sendable = [r for r in actionable if _should_send(r, tf)]
|
||||||
for report in actionable:
|
for report in sendable:
|
||||||
detail = format_signal_message(report)
|
_record_sent(report, tf)
|
||||||
try:
|
if sendable:
|
||||||
await bot.send_message(chat_id, detail, parse_mode=ParseMode.MARKDOWN)
|
for chat_id in list(_autoscan_chats):
|
||||||
except Exception as exc:
|
for report in sendable:
|
||||||
logger.warning(f"Failed to send signal for chat {chat_id}: {exc}")
|
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 ──────────────────────────────────
|
# ── WebSocket level-breach monitor ──────────────────────────────────
|
||||||
@@ -563,6 +602,11 @@ _AUTOSCAN_LOOPS = [
|
|||||||
(Timeframe.H4, 240),
|
(Timeframe.H4, 240),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
_TF_SECONDS = {
|
||||||
|
Timeframe.M5: 300, Timeframe.M15: 900,
|
||||||
|
Timeframe.H1: 3600, Timeframe.H4: 14400,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _start_scheduler(bot) -> None:
|
def _start_scheduler(bot) -> None:
|
||||||
global _dashboard_task
|
global _dashboard_task
|
||||||
|
|||||||
Reference in New Issue
Block a user