Files
signal-platform/tests/unit/test_schedule_config.py
T
dennisthiessen c34f3cb1a4
Deploy / lint (push) Successful in 9s
Deploy / test (push) Successful in 46s
Deploy / deploy (push) Successful in 28s
redesign activation gate to expected value + make pipelines cron-configurable
Diagnosing "no qualified signals for 5 days": setups were generated but none
qualified. The gate required BOTH a high min_rr (2.0) AND a high
min_target_probability (60), which became contradictory after the Jun-15
probability recalibration — probability already embeds R:R via the 1/(rr+1) ruin
term, so high-R:R targets are inherently low-probability and nothing cleared both.

Gate is now expected value (R): p*rr - (1-p) from the primary target's
probability. R:R and confidence stay as floors; high-conviction / exclude-conflicts
/ min-target-probability become optional tighteners (default off). Defaults:
min_expected_value=0.15, min_rr=1.2, min_confidence=55. EV is only enforced when
computable. Migration 009 clears stored activation_* rows so the new defaults
apply. Backtest sweeps min_expected_value instead of target probability.

Scheduling: pipelines are now cron-configurable in Admin -> Jobs. daily_pipeline
(full, default 0 7 * * *) plus a new light intraday_pipeline (OHLCV + outcome eval,
default hourly US session) that keeps prices/live-R:R current without setup churn.
Fundamentals on its own early weekly cron. Timezone configurable (default
Europe/Berlin). Moving interval->CronTrigger also fixes the restart-deferral bug
where an interval job's countdown resets on every process restart.

319 backend unit tests pass; frontend tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:46:38 +02:00

62 lines
2.2 KiB
Python

"""Unit tests for the cron pipeline schedule config."""
from __future__ import annotations
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from app.exceptions import ValidationError
from app.scheduler import SCHEDULE_DEFAULTS, validate_cron
from app.services.admin_service import get_schedule_config, update_schedule_config
@pytest.fixture
async def session() -> AsyncSession:
from tests.conftest import _test_session_factory
async with _test_session_factory() as session:
yield session
class TestValidateCron:
def test_accepts_valid(self):
validate_cron("0 7 * * *", "Europe/Berlin")
validate_cron("0 14-22 * * 1-5", "UTC")
def test_rejects_bad_cron(self):
with pytest.raises(Exception):
validate_cron("not a cron", "UTC")
def test_rejects_bad_timezone(self):
with pytest.raises(Exception):
validate_cron("0 7 * * *", "Mars/Phobos")
class TestScheduleConfig:
async def test_defaults_when_unset(self, session: AsyncSession):
config = await get_schedule_config(session)
assert config == SCHEDULE_DEFAULTS
async def test_update_and_read_back(self, session: AsyncSession):
updated = await update_schedule_config(
session, {"schedule_daily_pipeline_cron": "30 6 * * *"}
)
assert updated["schedule_daily_pipeline_cron"] == "30 6 * * *"
# untouched keys keep their defaults
assert updated["schedule_intraday_pipeline_cron"] == SCHEDULE_DEFAULTS["schedule_intraday_pipeline_cron"]
config = await get_schedule_config(session)
assert config["schedule_daily_pipeline_cron"] == "30 6 * * *"
async def test_rejects_bad_cron(self, session: AsyncSession):
with pytest.raises(ValidationError):
await update_schedule_config(session, {"schedule_fundamentals_cron": "every monday"})
async def test_rejects_bad_timezone(self, session: AsyncSession):
with pytest.raises(ValidationError):
await update_schedule_config(session, {"schedule_timezone": "Nowhere/Void"})
async def test_rejects_unknown_key(self, session: AsyncSession):
with pytest.raises(ValidationError):
await update_schedule_config(session, {"schedule_bogus": "0 0 * * *"})