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.
141 lines
5.2 KiB
Python
141 lines
5.2 KiB
Python
from __future__ import annotations
|
|
|
|
from libs.intraday.domain import DayResult, IntradayConfig, IntradayTrade, ORBStrategyParams
|
|
from libs.intraday.metrics import IntradayMetricsAccumulator, compute_metrics, format_summary
|
|
|
|
|
|
def test_no_trade_run_preserves_period_and_zero_return_metrics() -> None:
|
|
config = IntradayConfig(
|
|
strategy_mode="orb",
|
|
orb_strategy=ORBStrategyParams(initial_capital=10_000.0),
|
|
)
|
|
day_results = [
|
|
DayResult(date="2026-03-13", daily_pnl=0.0, daily_return_pct=0.0),
|
|
DayResult(date="2026-03-16", daily_pnl=0.0, daily_return_pct=0.0),
|
|
]
|
|
|
|
metrics = compute_metrics(day_results, config, run_id="test1234")
|
|
summary = format_summary(metrics, config)
|
|
|
|
assert metrics.run_id == "test1234"
|
|
assert metrics.start_date == "2026-03-13"
|
|
assert metrics.end_date == "2026-03-16"
|
|
assert metrics.trading_days == 2
|
|
assert metrics.days_with_trades == 0
|
|
assert metrics.total_trades == 0
|
|
assert metrics.total_return_pct == 0.0
|
|
assert metrics.annualized_return_pct == 0.0
|
|
assert metrics.avg_daily_return_pct == 0.0
|
|
assert metrics.max_drawdown_pct == 0.0
|
|
assert metrics.final_equity == 10_000.0
|
|
assert "2026-03-13" in summary
|
|
assert "2026-03-16" in summary
|
|
assert "2 days (0 with trades)" in summary
|
|
|
|
|
|
def test_metrics_accumulator_matches_batch_metrics() -> None:
|
|
config = IntradayConfig(
|
|
strategy_mode="orb",
|
|
orb_strategy=ORBStrategyParams(initial_capital=10_000.0),
|
|
)
|
|
trade = IntradayTrade(
|
|
date="2026-03-13",
|
|
ticker="AAA",
|
|
entry_price=100.0,
|
|
exit_price=101.0,
|
|
entry_time="2026-03-13T09:35:00-05:00",
|
|
exit_time="2026-03-13T15:55:00-05:00",
|
|
shares=10,
|
|
pnl=10.0,
|
|
pnl_pct=0.01,
|
|
exit_reason="close",
|
|
)
|
|
day_results = [
|
|
DayResult(
|
|
date="2026-03-13",
|
|
trades=[trade],
|
|
daily_pnl=10.0,
|
|
daily_return_pct=0.001,
|
|
capital_deployed=1_000.0,
|
|
),
|
|
DayResult(
|
|
date="2026-03-16",
|
|
daily_pnl=-5.0,
|
|
daily_return_pct=-0.0005,
|
|
capital_deployed=0.0,
|
|
),
|
|
]
|
|
|
|
batch = compute_metrics(day_results, config, run_id="batch")
|
|
accumulator = IntradayMetricsAccumulator(config, run_id="stream")
|
|
accumulator.extend(day_results)
|
|
stream = accumulator.finalize()
|
|
|
|
assert stream.start_date == batch.start_date
|
|
assert stream.end_date == batch.end_date
|
|
assert stream.total_trades == batch.total_trades
|
|
assert stream.days_with_trades == batch.days_with_trades
|
|
assert stream.final_equity == batch.final_equity
|
|
assert stream.total_return_pct == batch.total_return_pct
|
|
|
|
|
|
def test_metrics_accumulator_snapshot_roundtrip() -> None:
|
|
config = IntradayConfig(
|
|
strategy_mode="orb",
|
|
orb_strategy=ORBStrategyParams(initial_capital=10_000.0),
|
|
)
|
|
day_results = [
|
|
DayResult(date="2026-03-13", daily_pnl=12.0, daily_return_pct=0.0012),
|
|
DayResult(date="2026-03-16", daily_pnl=-4.0, daily_return_pct=-0.0004),
|
|
]
|
|
|
|
accumulator = IntradayMetricsAccumulator(config, run_id="snap")
|
|
accumulator.extend(day_results)
|
|
restored = IntradayMetricsAccumulator.from_snapshot(
|
|
config,
|
|
accumulator.snapshot(),
|
|
run_id="restored",
|
|
)
|
|
|
|
assert restored.run_id == "restored"
|
|
assert restored.n_days == accumulator.n_days
|
|
assert restored.days_with_trades == accumulator.days_with_trades
|
|
assert restored.daily_returns == accumulator.daily_returns
|
|
assert restored.equity == accumulator.equity
|
|
assert restored.max_drawdown == accumulator.max_drawdown
|
|
assert restored.finalize().final_equity == accumulator.finalize().final_equity
|
|
|
|
|
|
def test_metrics_compute_loss_containment_fields() -> None:
|
|
config = IntradayConfig(
|
|
strategy_mode="orb",
|
|
orb_strategy=ORBStrategyParams(initial_capital=10_000.0),
|
|
)
|
|
base_trade = IntradayTrade(
|
|
date="2026-03-13",
|
|
ticker="AAA",
|
|
entry_price=100.0,
|
|
exit_price=101.0,
|
|
entry_time="2026-03-13T09:35:00-05:00",
|
|
exit_time="2026-03-13T15:55:00-05:00",
|
|
shares=10,
|
|
pnl=10.0,
|
|
pnl_pct=0.01,
|
|
exit_reason="close",
|
|
)
|
|
day_results = [
|
|
DayResult(date="2026-03-13", trades=[base_trade], daily_pnl=100.0, daily_return_pct=0.01),
|
|
DayResult(date="2026-03-16", trades=[base_trade.model_copy(update={"date": "2026-03-16", "pnl": -50.0, "pnl_pct": -0.005})], daily_pnl=-50.0, daily_return_pct=-0.005),
|
|
DayResult(date="2026-03-17", trades=[base_trade.model_copy(update={"date": "2026-03-17", "pnl": -200.0, "pnl_pct": -0.02})], daily_pnl=-200.0, daily_return_pct=-0.02),
|
|
DayResult(date="2026-03-18", trades=[base_trade.model_copy(update={"date": "2026-03-18", "pnl": 0.0, "pnl_pct": 0.0})], daily_pnl=0.0, daily_return_pct=0.0),
|
|
]
|
|
|
|
metrics = compute_metrics(day_results, config, run_id="lossctl")
|
|
|
|
assert metrics.loss_day_rate == 0.5
|
|
assert metrics.avg_loss_day_pct == -0.0125
|
|
assert metrics.tail_loss_20_pct == -0.02
|
|
assert metrics.worst_day_return_pct == -0.02
|
|
assert metrics.loss_containment_score is not None
|
|
assert 0.0 < metrics.loss_containment_score < 100.0
|