You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
77 lines
2.7 KiB
Python
77 lines
2.7 KiB
Python
from __future__ import annotations
|
|
|
|
import datetime as dt
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from apps.web.routers import orb_trading
|
|
from libs.oracle_client.alpaca import AlpacaSnapshot
|
|
|
|
|
|
class _FakeORBState:
|
|
def __init__(self, positions: list[SimpleNamespace]) -> None:
|
|
self._positions = positions
|
|
|
|
def get_session(self, session_id: str) -> SimpleNamespace | None:
|
|
return SimpleNamespace(session_id=session_id, session_name="test")
|
|
|
|
def get_open_positions(self, session_id: str, date: str) -> list[SimpleNamespace]:
|
|
assert date == dt.date.today().isoformat()
|
|
return self._positions
|
|
|
|
|
|
def _position(
|
|
ticker: str,
|
|
*,
|
|
direction: str,
|
|
entry_price: float,
|
|
shares: int = 2,
|
|
) -> SimpleNamespace:
|
|
return SimpleNamespace(
|
|
ticker=ticker,
|
|
direction=direction,
|
|
entry_price=entry_price,
|
|
entry_time="2026-05-04T09:59:00-04:00",
|
|
shares=shares,
|
|
current_stop=entry_price * 0.95,
|
|
peak_price=entry_price,
|
|
trailing_active=False,
|
|
atr_at_entry=1.0,
|
|
stop_distance=5.0,
|
|
rvol=1.0,
|
|
composite_score=0.5,
|
|
)
|
|
|
|
|
|
def test_orb_positions_change_pct_is_position_return_not_day_change(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
positions = [
|
|
_position("LONG", direction="long", entry_price=100.0),
|
|
_position("SHORT", direction="short", entry_price=100.0),
|
|
]
|
|
monkeypatch.setattr(orb_trading, "_state", lambda: _FakeORBState(positions))
|
|
|
|
def fake_snapshots(tickers: list[str]) -> dict[str, AlpacaSnapshot]:
|
|
assert tickers == ["LONG", "SHORT"]
|
|
return {
|
|
# Positive day change must not make a losing long look profitable.
|
|
"LONG": AlpacaSnapshot(ticker="LONG", price=95.0, change_pct=12.34),
|
|
# Negative day change must not make a winning short look losing.
|
|
"SHORT": AlpacaSnapshot(ticker="SHORT", price=95.0, change_pct=-7.89),
|
|
}
|
|
|
|
monkeypatch.setattr("libs.oracle_client.alpaca.get_snapshots", fake_snapshots)
|
|
|
|
result = orb_trading.get_positions("session-1")
|
|
by_ticker = {p["ticker"]: p for p in result["positions"]}
|
|
|
|
assert by_ticker["LONG"]["current_price"] == pytest.approx(95.0)
|
|
assert by_ticker["LONG"]["change_pct"] == pytest.approx(-5.0)
|
|
assert by_ticker["LONG"]["unrealized_pnl"] == pytest.approx(-10.0)
|
|
assert by_ticker["LONG"]["day_change_pct"] == pytest.approx(12.34)
|
|
|
|
assert by_ticker["SHORT"]["current_price"] == pytest.approx(95.0)
|
|
assert by_ticker["SHORT"]["change_pct"] == pytest.approx(5.0)
|
|
assert by_ticker["SHORT"]["unrealized_pnl"] == pytest.approx(10.0)
|
|
assert by_ticker["SHORT"]["day_change_pct"] == pytest.approx(-7.89)
|