feat: require gate reset before post-stop reentry
This commit is contained in:
@@ -20,7 +20,6 @@ from hypothesis import given, settings, HealthCheck, strategies as st
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.benchmark_price import BenchmarkPrice
|
||||
from app.models.ohlcv import OHLCVRecord
|
||||
from app.models.paper_trade import PaperTrade
|
||||
from app.models.signal_context_snapshot import SignalContextSnapshot
|
||||
@@ -609,11 +608,10 @@ async def test_get_trade_setups_can_exclude_tickers_with_open_paper_trades(
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_trade_setups_applies_five_session_initial_stop_lockdown(
|
||||
async def test_get_trade_setups_applies_initial_stop_gate_reset_lock(
|
||||
db_session: AsyncSession,
|
||||
):
|
||||
now = datetime.now(timezone.utc)
|
||||
today = now.date()
|
||||
if await db_session.get(User, 1) is None:
|
||||
db_session.add(
|
||||
User(id=1, username="u", password_hash="x", role="user", has_access=True)
|
||||
@@ -626,38 +624,6 @@ async def test_get_trade_setups_applies_five_session_initial_stop_lockdown(
|
||||
db_session.add_all([blocked, released, trailing])
|
||||
await db_session.flush()
|
||||
|
||||
# Six SPY sessions D0..D5 form the canonical market calendar. A stop on
|
||||
# D0 has five later sessions and is released; a stop on D1 has only four.
|
||||
market_sessions = [
|
||||
today - timedelta(days=8),
|
||||
today - timedelta(days=7),
|
||||
today - timedelta(days=6),
|
||||
today - timedelta(days=3),
|
||||
today - timedelta(days=2),
|
||||
today - timedelta(days=1),
|
||||
]
|
||||
for market_date in market_sessions:
|
||||
db_session.add(
|
||||
BenchmarkPrice(
|
||||
symbol="SPY",
|
||||
date=market_date,
|
||||
close=400.0,
|
||||
)
|
||||
)
|
||||
# A bar from an unrelated/scanner-specific calendar must not release the
|
||||
# ticker one session early. The old universe-wide DISTINCT query did.
|
||||
db_session.add(
|
||||
OHLCVRecord(
|
||||
ticker_id=blocked.id,
|
||||
date=today,
|
||||
open=100.0,
|
||||
high=101.0,
|
||||
low=99.0,
|
||||
close=100.0,
|
||||
volume=1_000,
|
||||
)
|
||||
)
|
||||
|
||||
for ticker in (blocked, released, trailing):
|
||||
db_session.add(
|
||||
TradeSetup(
|
||||
@@ -672,7 +638,13 @@ async def test_get_trade_setups_applies_five_session_initial_stop_lockdown(
|
||||
)
|
||||
)
|
||||
|
||||
def closed_trade(ticker: Ticker, closed_on: date, reason: str) -> PaperTrade:
|
||||
def closed_trade(
|
||||
ticker: Ticker,
|
||||
reason: str,
|
||||
*,
|
||||
gate_reset_complete: bool = False,
|
||||
) -> PaperTrade:
|
||||
closed_on = now.date() - timedelta(days=10)
|
||||
return PaperTrade(
|
||||
user_id=1,
|
||||
ticker_id=ticker.id,
|
||||
@@ -690,13 +662,19 @@ async def test_get_trade_setups_applies_five_session_initial_stop_lockdown(
|
||||
closed_on, datetime.min.time(), tzinfo=timezone.utc
|
||||
),
|
||||
close_reason=reason,
|
||||
reentry_gate_failed_at=(
|
||||
now - timedelta(days=9) if gate_reset_complete else None
|
||||
),
|
||||
reentry_gate_requalified_at=(
|
||||
now - timedelta(days=8) if gate_reset_complete else None
|
||||
),
|
||||
)
|
||||
|
||||
db_session.add_all(
|
||||
[
|
||||
closed_trade(blocked, market_sessions[1], "stop"),
|
||||
closed_trade(released, market_sessions[0], "stop"),
|
||||
closed_trade(trailing, market_sessions[-1], "trailing"),
|
||||
closed_trade(blocked, "stop"),
|
||||
closed_trade(released, "stop", gate_reset_complete=True),
|
||||
closed_trade(trailing, "trailing"),
|
||||
]
|
||||
)
|
||||
await db_session.flush()
|
||||
@@ -710,7 +688,7 @@ async def test_get_trade_setups_applies_five_session_initial_stop_lockdown(
|
||||
row["symbol"]
|
||||
for row in await get_trade_setups(
|
||||
db_session,
|
||||
exclude_reentry_lockdown_tickers=True,
|
||||
exclude_reentry_gate_locked_tickers=True,
|
||||
)
|
||||
}
|
||||
assert "STOP4" not in available_symbols
|
||||
@@ -719,10 +697,10 @@ async def test_get_trade_setups_applies_five_session_initial_stop_lockdown(
|
||||
annotated = await get_trade_setups(
|
||||
db_session,
|
||||
symbol="STOP4",
|
||||
include_reentry_lockdown=True,
|
||||
include_reentry_gate_lock=True,
|
||||
)
|
||||
assert len(annotated) == 1
|
||||
assert annotated[0]["reentry_lockdown_remaining_sessions"] == 1
|
||||
assert annotated[0]["reentry_gate_reset_required"] is True
|
||||
|
||||
|
||||
async def _seed_stale_setup_with_current_scores(db_session: AsyncSession) -> TradeSetup:
|
||||
|
||||
Reference in New Issue
Block a user