All 12 modules built per reviewed plan: - detector, state_machine (5-state phased FSM), canary, levels Phase B - notifier fanout (Discord + Telegram, bounded queue, retry, dead-letter) - audit (JSONL daily rotation), journal, report (weekly R-multiple PnL) - calibrate + labeler (Tk, lazy-imported), dryrun with acceptance gate - unified CLI: atm calibrate|label|dryrun|run|journal|report README + Phase 2 prop-firm TOS audit checklist included. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
77 lines
2.2 KiB
Python
77 lines
2.2 KiB
Python
"""Tests for atm.report."""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from atm.journal import TradeEntry
|
|
from atm.report import iso_week, weekly_report
|
|
|
|
WEEK = "2026-16"
|
|
BASE_TS = "2026-04-14T10:00:00"
|
|
|
|
|
|
def _trade(
|
|
outcome: str,
|
|
direction: str = "BUY",
|
|
entry: float = 100.0,
|
|
sl: float = 90.0,
|
|
exit_: float | None = None,
|
|
detected_ts: str | None = None,
|
|
ts: str = BASE_TS,
|
|
) -> TradeEntry:
|
|
return TradeEntry(
|
|
ts=ts,
|
|
direction=direction,
|
|
symbol="US30",
|
|
entry=entry,
|
|
sl=sl,
|
|
tp1=None,
|
|
tp2=None,
|
|
exit=exit_,
|
|
outcome=outcome,
|
|
detected_ts=detected_ts,
|
|
notes="",
|
|
)
|
|
|
|
|
|
def test_win_rate_and_pnl() -> None:
|
|
"""5 synthetic trades: tp1 +2R, tp2 +3R, sl -1.5R, manual +2R, open excluded."""
|
|
trades = [
|
|
# tp1: BUY, entry=100, sl=90, exit=120 → R = (120-100)/(100-90) = +2.0
|
|
_trade("tp1", exit_=120.0, detected_ts="2026-04-14T09:59:55"),
|
|
# tp2: BUY, entry=100, sl=90, exit=130 → R = (130-100)/(100-90) = +3.0
|
|
_trade("tp2", exit_=130.0),
|
|
# sl: BUY, entry=100, sl=90, exit=85 → R = (85-100)/(100-90) = -1.5
|
|
_trade("sl", exit_=85.0, detected_ts="2026-04-14T09:59:50"),
|
|
# manual: SELL, entry=100, sl=110, exit=80 → R = (80-100)/(100-110) = +2.0
|
|
_trade("manual", direction="SELL", sl=110.0, exit_=80.0),
|
|
# open: excluded from counts
|
|
_trade("open"),
|
|
]
|
|
|
|
report = weekly_report(trades, WEEK)
|
|
|
|
assert report.week == WEEK
|
|
assert report.n_trades == 4
|
|
assert report.n_wins == 2
|
|
assert report.n_losses == 2
|
|
assert report.win_rate == pytest.approx(0.5)
|
|
assert report.pnl_r == pytest.approx(5.5)
|
|
# slippage: trade[0]=5s, trade[2]=10s → avg=7.5s
|
|
assert report.avg_slippage == pytest.approx(7.5)
|
|
|
|
|
|
def test_iso_week() -> None:
|
|
assert iso_week("2026-04-14T10:00:00") == "2026-16"
|
|
assert iso_week("2026-01-01T00:00:00") == "2026-01"
|
|
|
|
|
|
def test_empty_week() -> None:
|
|
report = weekly_report([], WEEK)
|
|
assert report.n_trades == 0
|
|
assert report.n_wins == 0
|
|
assert report.n_losses == 0
|
|
assert report.win_rate == 0.0
|
|
assert report.pnl_r == 0.0
|
|
assert report.avg_slippage is None
|