feat: add five-session post-stop reentry lockdown
This commit is contained in:
@@ -607,6 +607,99 @@ async def test_get_trade_setups_can_exclude_tickers_with_open_paper_trades(
|
||||
assert [row["symbol"] for row in ticker_rows] == ["OPENQ"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_trade_setups_applies_five_session_initial_stop_lockdown(
|
||||
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)
|
||||
)
|
||||
await db_session.flush()
|
||||
|
||||
blocked = Ticker(symbol="STOP4")
|
||||
released = Ticker(symbol="STOP5")
|
||||
trailing = Ticker(symbol="TRAILQ")
|
||||
db_session.add_all([blocked, released, trailing])
|
||||
await db_session.flush()
|
||||
|
||||
# Six synthetic stored market sessions D0..D5. A stop on D0 has five
|
||||
# later sessions and is released; a stop on D1 has only four and is not.
|
||||
market_sessions = [today - timedelta(days=offset) for offset in range(5, -1, -1)]
|
||||
for market_date in market_sessions:
|
||||
db_session.add(
|
||||
OHLCVRecord(
|
||||
ticker_id=blocked.id,
|
||||
date=market_date,
|
||||
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(
|
||||
ticker_id=ticker.id,
|
||||
direction="long",
|
||||
entry_price=100.0,
|
||||
stop_loss=95.0,
|
||||
target=115.0,
|
||||
rr_ratio=3.0,
|
||||
composite_score=80.0,
|
||||
detected_at=now,
|
||||
)
|
||||
)
|
||||
|
||||
def closed_trade(ticker: Ticker, closed_on: date, reason: str) -> PaperTrade:
|
||||
return PaperTrade(
|
||||
user_id=1,
|
||||
ticker_id=ticker.id,
|
||||
direction="long",
|
||||
entry_price=100.0,
|
||||
shares=10.0,
|
||||
stop_loss=95.0,
|
||||
target=115.0,
|
||||
status="closed",
|
||||
opened_at=datetime.combine(
|
||||
closed_on - timedelta(days=1), datetime.min.time(), tzinfo=timezone.utc
|
||||
),
|
||||
close_price=95.0,
|
||||
closed_at=datetime.combine(
|
||||
closed_on, datetime.min.time(), tzinfo=timezone.utc
|
||||
),
|
||||
close_reason=reason,
|
||||
)
|
||||
|
||||
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"),
|
||||
]
|
||||
)
|
||||
await db_session.flush()
|
||||
|
||||
default_symbols = {
|
||||
row["symbol"] for row in await get_trade_setups(db_session)
|
||||
}
|
||||
assert {"STOP4", "STOP5", "TRAILQ"}.issubset(default_symbols)
|
||||
|
||||
available_symbols = {
|
||||
row["symbol"]
|
||||
for row in await get_trade_setups(
|
||||
db_session,
|
||||
exclude_reentry_lockdown_tickers=True,
|
||||
)
|
||||
}
|
||||
assert "STOP4" not in available_symbols
|
||||
assert {"STOP5", "TRAILQ"}.issubset(available_symbols)
|
||||
|
||||
|
||||
async def _seed_stale_setup_with_current_scores(db_session: AsyncSession) -> TradeSetup:
|
||||
"""Stored setup frozen at scan time (conf 82, neutral) vs. current context
|
||||
(bullish sentiment, composite 96) that yields live confidence 97.
|
||||
|
||||
Reference in New Issue
Block a user