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:
Claude Agent
2026-04-15 22:17:41 +00:00
parent 9207197a56
commit bf70ca3ac7
22 changed files with 2634 additions and 0 deletions

95
tests/test_journal.py Normal file
View 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])