Added a simple ui
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
from datetime import UTC, date, datetime, timedelta
|
||||
|
||||
from trading_bot.data.alpaca_daily_lib import DailyCandle
|
||||
from trading_bot.ui.dashboard import (
|
||||
DashboardSnapshot,
|
||||
PerformancePoint,
|
||||
PortfolioRow,
|
||||
TradeRow,
|
||||
_benchmark_end_date,
|
||||
_from_unix_timestamp,
|
||||
_normalize_benchmark,
|
||||
is_cache_stale,
|
||||
read_snapshot,
|
||||
render_dashboard,
|
||||
write_snapshot,
|
||||
)
|
||||
|
||||
|
||||
def test_normalize_benchmark_scales_spy_to_account_start_value() -> None:
|
||||
candles = [
|
||||
DailyCandle(date(2026, 5, 1), 100.0, 100.0, 100.0, 100.0, 1_000),
|
||||
DailyCandle(date(2026, 5, 2), 110.0, 110.0, 110.0, 110.0, 1_000),
|
||||
]
|
||||
|
||||
values = _normalize_benchmark(
|
||||
candles,
|
||||
start_day=date(2026, 5, 1),
|
||||
initial_account_value=1_000.0,
|
||||
)
|
||||
|
||||
assert values[date(2026, 5, 1)] == 1_000.0
|
||||
assert values[date(2026, 5, 2)] == 1_100.0
|
||||
|
||||
|
||||
def test_portfolio_history_timestamps_are_interpreted_as_eastern_dates() -> None:
|
||||
timestamp = datetime(2026, 8, 11, 2, 30, tzinfo=UTC).timestamp()
|
||||
|
||||
assert _from_unix_timestamp(timestamp) == date(2026, 8, 10)
|
||||
|
||||
|
||||
def test_benchmark_end_date_does_not_go_past_completed_market_day(monkeypatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
"trading_bot.ui.dashboard.default_end_date",
|
||||
lambda: date(2026, 8, 10),
|
||||
)
|
||||
|
||||
assert _benchmark_end_date(date(2026, 8, 11)) == date(2026, 8, 10)
|
||||
assert _benchmark_end_date(date(2026, 8, 8)) == date(2026, 8, 8)
|
||||
|
||||
|
||||
def test_sqlite_snapshot_round_trip(tmp_path) -> None:
|
||||
db_path = tmp_path / "dashboard.sqlite"
|
||||
refreshed_at = datetime(2026, 8, 11, 10, 0, tzinfo=UTC)
|
||||
snapshot = DashboardSnapshot(
|
||||
refreshed_at=refreshed_at,
|
||||
performance=[
|
||||
PerformancePoint(date(2026, 8, 10), 10_000.0, 9_900.0),
|
||||
PerformancePoint(date(2026, 8, 11), 10_100.0, 10_000.0),
|
||||
],
|
||||
portfolio=[
|
||||
PortfolioRow("CASH", "cash", None, 500.0, None, None, None),
|
||||
PortfolioRow("SPY", "us_equity", 10.0, 5_000.0, 490.0, 500.0, 100.0),
|
||||
],
|
||||
trades=[
|
||||
TradeRow(
|
||||
datetime(2026, 8, 11, 9, 30, tzinfo=UTC),
|
||||
"SPY",
|
||||
"buy",
|
||||
1.0,
|
||||
500.0,
|
||||
500.0,
|
||||
"filled",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
write_snapshot(db_path, snapshot)
|
||||
loaded = read_snapshot(db_path)
|
||||
|
||||
assert loaded.refreshed_at == refreshed_at
|
||||
assert loaded.performance == snapshot.performance
|
||||
assert loaded.portfolio == snapshot.portfolio
|
||||
assert loaded.trades == snapshot.trades
|
||||
|
||||
|
||||
def test_cache_stale_after_refresh_interval(tmp_path) -> None:
|
||||
db_path = tmp_path / "dashboard.sqlite"
|
||||
refreshed_at = datetime(2026, 8, 11, 0, 0, tzinfo=UTC)
|
||||
snapshot = DashboardSnapshot(
|
||||
refreshed_at=refreshed_at,
|
||||
performance=[],
|
||||
portfolio=[],
|
||||
trades=[],
|
||||
)
|
||||
write_snapshot(db_path, snapshot)
|
||||
|
||||
assert not is_cache_stale(
|
||||
db_path,
|
||||
timedelta(hours=12),
|
||||
now=refreshed_at + timedelta(hours=11, minutes=59),
|
||||
)
|
||||
assert is_cache_stale(
|
||||
db_path,
|
||||
timedelta(hours=12),
|
||||
now=refreshed_at + timedelta(hours=12),
|
||||
)
|
||||
|
||||
|
||||
def test_render_dashboard_contains_requested_sections() -> None:
|
||||
html = render_dashboard(
|
||||
DashboardSnapshot(
|
||||
refreshed_at=datetime(2026, 8, 11, 10, 0, tzinfo=UTC),
|
||||
performance=[
|
||||
PerformancePoint(date(2026, 8, 10), 10_000.0, 10_000.0),
|
||||
PerformancePoint(date(2026, 8, 11), 10_250.0, 10_100.0),
|
||||
],
|
||||
portfolio=[PortfolioRow("CASH", "cash", None, 250.0, None, None, None)],
|
||||
trades=[
|
||||
TradeRow(
|
||||
datetime(2026, 8, 11, 9, 30, tzinfo=UTC),
|
||||
"SPY",
|
||||
"orderside.sell",
|
||||
1.0,
|
||||
500.0,
|
||||
500.0,
|
||||
"orderstatus.filled",
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
assert "Account Value vs S&P 500 Proxy" in html
|
||||
assert "Portfolio" in html
|
||||
assert "Completed Trades" in html
|
||||
assert "+$250.00 (+2.50%)" in html
|
||||
assert "<th>Type</th>" not in html
|
||||
assert "<th>Account %</th>" in html
|
||||
assert "SELL</span>" in html
|
||||
assert "FILLED</span>" in html
|
||||
assert "orderside.sell" not in html
|
||||
assert "orderstatus.filled" not in html
|
||||
Reference in New Issue
Block a user