feat: complete Faza 1 implementation (105 tests green)
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>
This commit is contained in:
95
tests/test_journal.py
Normal file
95
tests/test_journal.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""Tests for atm.journal."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from atm.journal import Journal, TradeEntry, prompt_entry
|
||||
|
||||
|
||||
def _sample() -> TradeEntry:
|
||||
return TradeEntry(
|
||||
ts="2026-04-14T10:00:00",
|
||||
direction="BUY",
|
||||
symbol="US30",
|
||||
entry=40000.0,
|
||||
sl=39950.0,
|
||||
tp1=40100.0,
|
||||
tp2=None,
|
||||
exit=None,
|
||||
outcome="open",
|
||||
detected_ts=None,
|
||||
notes="",
|
||||
)
|
||||
|
||||
|
||||
def test_add_and_read_roundtrip(tmp_path: Path) -> None:
|
||||
journal = Journal(tmp_path / "trades.jsonl")
|
||||
e1 = TradeEntry(
|
||||
ts="2026-04-14T10:00:00", direction="BUY", symbol="US30",
|
||||
entry=40000.0, sl=39950.0, tp1=40100.0, tp2=None,
|
||||
exit=40100.0, outcome="tp1", detected_ts=None, notes="",
|
||||
)
|
||||
e2 = TradeEntry(
|
||||
ts="2026-04-14T11:00:00", direction="SELL", symbol="NQ",
|
||||
entry=20000.0, sl=20050.0, tp1=None, tp2=None,
|
||||
exit=20050.0, outcome="sl", detected_ts=None, notes="stop hit",
|
||||
)
|
||||
|
||||
journal.add(e1)
|
||||
journal.add(e2)
|
||||
|
||||
all_entries = journal.all()
|
||||
assert len(all_entries) == 2
|
||||
assert all_entries[0] == e1
|
||||
assert all_entries[1] == e2
|
||||
|
||||
|
||||
def test_prompt_entry_with_defaults() -> None:
|
||||
inputs = iter([
|
||||
"2026-04-15T10:30:00", # ts
|
||||
"BUY", # direction
|
||||
"US30", # symbol
|
||||
"40000", # entry
|
||||
"39950", # sl
|
||||
"40100", # tp1
|
||||
"", # tp2 (blank → None)
|
||||
"", # exit (blank → None)
|
||||
"open", # outcome
|
||||
"", # notes
|
||||
])
|
||||
|
||||
detected = {
|
||||
"direction": "BUY",
|
||||
"symbol": "US30",
|
||||
"detected_ts": "2026-04-15T10:29:45",
|
||||
}
|
||||
|
||||
entry = prompt_entry(input_fn=lambda _: next(inputs), detected=detected)
|
||||
|
||||
assert entry.direction == "BUY"
|
||||
assert entry.symbol == "US30"
|
||||
assert entry.entry == 40000.0
|
||||
assert entry.sl == 39950.0
|
||||
assert entry.tp1 == 40100.0
|
||||
assert entry.tp2 is None
|
||||
assert entry.exit is None
|
||||
assert entry.outcome == "open"
|
||||
assert entry.detected_ts == "2026-04-15T10:29:45"
|
||||
assert entry.notes == ""
|
||||
|
||||
|
||||
def test_file_line_buffered(tmp_path: Path) -> None:
|
||||
"""Each add() writes exactly one JSONL line, immediately readable."""
|
||||
path = tmp_path / "trades.jsonl"
|
||||
journal = Journal(path)
|
||||
|
||||
journal.add(_sample())
|
||||
lines = path.read_text(encoding="utf-8").splitlines()
|
||||
assert len(lines) == 1
|
||||
json.loads(lines[0]) # must be valid JSON
|
||||
|
||||
journal.add(_sample())
|
||||
lines = path.read_text(encoding="utf-8").splitlines()
|
||||
assert len(lines) == 2
|
||||
json.loads(lines[1])
|
||||
Reference in New Issue
Block a user